package com.example.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.common.core.constants.Constants;
import com.example.common.core.enums.ResultCode;
import com.example.common.core.exception.ServiceException;
import com.example.system.manger.ExamCacheManger;
import com.example.system.domain.exam.Exam;
import com.example.system.domain.exam.ExamQuestion;
import com.example.system.domain.exam.dto.ExamAddDTO;
import com.example.system.domain.exam.dto.ExamEditDTO;
import com.example.system.domain.exam.dto.ExamQueryDTO;
import com.example.system.domain.exam.dto.ExamQuestionAddDTO;
import com.example.system.domain.exam.vo.ExamDetailVO;
import com.example.system.domain.exam.vo.ExamVO;
import com.example.system.domain.question.Question;
import com.example.system.domain.question.vo.QuestionVO;
import com.example.system.mapper.exam.ExamMapper;
import com.example.system.mapper.exam.ExamQuestionMapper;
import com.example.system.mapper.question.QuestionMapper;
import com.example.system.service.exam.IExamService;

import com.github.pagehelper.PageHelper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper,ExamQuestion>  implements IExamService {
    @Autowired
    private ExamMapper examMapper;
    @Autowired
    private QuestionMapper questionMapper;
    @Autowired
    private ExamQuestionMapper examQuestionMapper;
    @Autowired
    private ExamCacheManger examCacheManger;
    //竞赛列表
    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {

        //设置自动分页查询
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        //从数据库中查询题目列表
        List<ExamVO> examVOList = examMapper.selectExamList(examQueryDTO);
        return examVOList;
    }

    //添加竞赛
    @Override
    public String add(ExamAddDTO examAddDTO) {
        checkExamSaveParms(examAddDTO,null);
        //对象转换
        Exam exam = new Exam();
        BeanUtils.copyProperties(examAddDTO, exam);
        //把题目存入数据库，并返回给controller层
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }

    //在竞赛里添加题目
    @Override
    public boolean questionAdd(ExamQuestionAddDTO examQuestionAddDTO) {
        //检查竞赛是否存在
        Exam exam = getExam(examQuestionAddDTO.getExamId());
        //竞赛已经开始了，就无法进行任何操作了
        checkExam(exam);
        //判断竞赛是否发布，如果发布则不能操作
        is_Published(exam);
        //判断一下传过来的questionId是否为空
        Set<Long> questionIdSet = examQuestionAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            throw  new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        //判断题目是否存在,如果题目不存在无法添加，采取批量操作的方式
        List<Question> questionList = questionMapper.selectBatchIds(questionIdSet);
        if (CollectionUtil.isNotEmpty(questionList) && questionList.size() != questionIdSet.size()) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
        //判断竞赛是否已经包含有这些题目

        //把竞赛题目存入数据库
        return saveExamQuestion(exam,questionIdSet);
    }


    //竞赛中的题目删除
    @Override
    public Integer questionDelete(Long examId, Long questionId) {
        Exam exam = getExam(examId);
        checkExam(exam);
        //判断竞赛是否发布，如果发布则不能操作
        is_Published(exam);
        //开始删除
        Integer n = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>().
                eq(ExamQuestion::getExamId,examId).
                eq(ExamQuestion::getQuestionId,questionId));
        return n;
    }

    //竞赛详情
    @Override
    public ExamDetailVO detail(Long examId) {
        //先把竞赛基本信息
        Exam exam = getExam(examId);
        ExamDetailVO examDetailVO = new ExamDetailVO();
        BeanUtils.copyProperties(exam,examDetailVO);
        //获取竞赛的题目信息
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                        .select(ExamQuestion::getQuestionId)
                .eq(ExamQuestion::getExamId,examId).orderByAsc(ExamQuestion::getQuestionOrder));
        if (CollectionUtil.isEmpty(examQuestionList)) {
            //如果竞赛中的题目列表为空，返回竞赛详情就行
            return examDetailVO;
        }
        //如果不为空,用流的方式把questionId转为一个新的集合
        List<Long> questionIdList = examQuestionList.stream().map(ExamQuestion::getQuestionId).toList();

        //根据id集合，查询题目详情，这里用的批量查询
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>().
                        select(Question::getQuestionId, Question::getTitle, Question::getDifficulty).
                in(Question::getQuestionId,questionIdList));

        List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList,QuestionVO.class);
        examDetailVO.setExamQuestionList(questionVOList);
        return examDetailVO;
    }


    //编辑竞赛
    @Override
    public Integer edit(ExamEditDTO examEditDTO) {
        Exam exam = getExam(examEditDTO.getExamId());
        //判断竞赛是否发布，如果发布则不能操作
        is_Published(exam);
        //检查参数是否合法
        checkExamSaveParms(examEditDTO,examEditDTO.getExamId());
        //竞赛已经开始了，就无法进行任何操作了
        checkExam(exam);
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());
        return examMapper.updateById(exam);
    }

    //删除竞赛
    @Override
    public Integer delete(Long examId) {
        //判断是否存在
        Exam exam = getExam(examId);
        //判断竞赛是否发布，如果发布则不能操作
        is_Published(exam);
        //判断竞赛是否开赛
        checkExam(exam);
        Integer n = examMapper.deleteById(examId);
        //删除竞赛的题目
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId,examId));
        return n;
    }

    //发布竞赛
    @Override
    public Integer publish(Long examId) {
        //判断竞赛是否存在，不存在步能操作
        Exam exam = getExam(examId);
        //检查竞赛是否开始，如果已经开始则不能发布
        checkExam(exam);
        //判断该竞赛是否有题目
        Long count = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId,examId));
        //如果没题目则不能发布
        if (count == null || count == 0) {
            throw  new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }
        exam.setStatus(Constants.TRUE);
        //将发布的竞赛存在redis当中 ， 存两个
        //第一个key为e:t:l ，value为examId，不存储真实的竞赛内容
        //第二个key为e:d:examId value为具体的竞赛详细信息
        examCacheManger.addCache(exam);
        return examMapper.updateById(exam);
    }


    //撤销发布
    @Override
    public Integer cancelPublish(Long examId) {
        //竞赛是否存在
        Exam exam = getExam(examId);
        //检查竞赛是否完成，如果已经结束则不能取消发布
        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }
        //竞赛是否开始
        checkExam(exam);
        exam.setStatus(Constants.FALSE);
        //把竞赛从redis里删除
        examCacheManger.deleteCache(exam.getExamId());
        return examMapper.updateById(exam);
    }

    private void is_Published(Exam exam) {
        if(Constants.TRUE.equals(exam.getStatus())){
            throw  new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
    }

    //检查
    private void checkExamSaveParms(ExamAddDTO examSaveDTO, Long examId) {
        //判断一下时间，开始时间不能早于当前时间
        if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())){
            throw new ServiceException(ResultCode.EXAM_STARTTIME_BEFORE_CURRET_TIME);
        }
        if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())){
            throw new ServiceException(ResultCode.EXAM_STARTTIME_AFTER_END_TIME);
        }
        //判断竞赛是否的标题重复，但是对于更新操作来说，如果是自己的标题，那肯定是可以重复的，所以要排除自己
        //根据标题从数据库中查询相应的题目
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .eq(Exam::getTitle, examSaveDTO.getTitle())
                .ne(examId != null,Exam::getExamId,examId));
        //如果能查到该题目，说明重复了
        if (CollectionUtil.isNotEmpty(examList))   {
            throw  new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }
    }

    //对竞赛本身进行校验，竞赛已经开始了，就无法进行任何操作了
    private void checkExam(Exam exam){
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw  new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    //通过examId从数据库中获取到exam详细信息
    private Exam getExam(Long examId){
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }



    //封装一个方法把竞赛题目插入到数据库关联表中
    private boolean saveExamQuestion(Exam exam,Set<Long> questionIdSet){
        List<ExamQuestion> examQuestionList = new ArrayList<>();

        int num = 1;
        for (Long questionId : questionIdSet) {
            //开始添加竞赛题目
            //构造映射类对象
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId) ;
            examQuestion.setQuestionOrder(num++);
            examQuestionList.add(examQuestion);
        }
        //插入也采用批量插入的方法,mybatis扩展包里
        saveBatch(examQuestionList);
        return true;
    }
}
